#include "mainwindow.h"
#include "ui_mainwindow.h"
#include"QFileDialog"
#include<QDebug>

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<fstream>

using namespace std;

//构造函数
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow){

    //显示图形界面
    ui->setupUi(this);
}

//析构函数
MainWindow::~MainWindow(){

    delete ui;
}


//重置哈希表
void MainWindow::resethash(int n){

    //n==0表示完全重置哈希表
    if (n == 0){

        //把哈希表的关键字指针置空和频数置零
        for (int i = 0; i < 41; i++){

            hasht[i].hash1 = NULL;
            hasht[i].count = 0;
            hasht[i].count1 = 0;
        }
        for(int i=0;i<200;i++){
            hasht1[i].hash1 = NULL;
            hasht1[i].count = 0;
            hasht1[i].count1 = 0;
        }
    }
    //n==1仅仅重置频数
    else if (n == 1){

        //仅仅哈希表频数置零
        for (int i = 0; i < 41; i++){

            hasht[i].count = 0;
        }
    }
}

//拷贝频数
void MainWindow::copycount(int x[], int n){

    //将哈希表中的每个频数赋到数组X[]中
    for (int i = 0; i < n; i++){

        x[i] = hasht[i].count;
    }
}


//根据哈希函数获取该单词的关键码key，确定散列地址
int MainWindow::getkey(char* str, int len){

    char key1, key2;
    int key;

    key1 = str[0];          //关键词首字母
    key2 = str[len - 1];    //关键词尾字母

    //通过哈希函数得到目标单词在哈希表中的位置
    key = (int)(key1 * 100 + key2) % 41;

    return key;
}

//根据哈希函数获取该单词的关键码key，确定散列地址
int MainWindow::getkey1(char* str, int len){

    char key1, key2;
    int key;

    key1 = str[0];          //关键词首字母
    key2 = str[len - 1];    //关键词尾字母

    //通过哈希函数得到目标单词在哈希表中的位置
    key = (int)(key1 * 100 + key2) % 81;

    return key;
}

//对文件keyword.txt中的关键字创建哈希表
void MainWindow::creathash(void){

    FILE* fp;           //声明指向文件的指针

    int length;         //每一次读取数据（数组）的长度
    char str[SIZE];     //暂时存储关键字字符的数组

    //将暂时存储关键字字符的数组的每个字符赋值'\0'表字符结尾
    for (int i = 0; i < SIZE; i++){

        str[i] = '\0';
    }

    /*
     * 因为fopen（const char*）的参数是const char*类型，
     * 所以需要做如下两个转化
    */

    //将QString类型的字符串转成string类型
    string keyname= keyword_file.toStdString();

    //将string类型的字符串转成const char*;
    const char* KNAM = keyname.c_str();

    if ((fp = fopen(KNAM, "r")) == NULL){

        QString QS = "keywordfalse";
        ui->label_6->setText(QS);
    }

    /*
     * fgets()的功能是从fp所指向的文件流中
     * 读取SIZE（256）个字符存储到字符数组str中，
     * 读取一行写入一行
    */
    while (fgets(str, SIZE, fp) != NULL){

      //如果读取的str为空，退出循环，不在读取文件
      if (str == NULL){

            break;
      }

      //每一次读取数据（数组）的长度
      length = strlen(str);

      /*
       * 每次读取一个关键字，会多读取一个回车字符'\n'
       * 这就给后续字符串比较，造成麻烦
       * 所以，将读取字符数组的最后一个置'\0'
      */
      str[length - 1] = '\0';

      //将关键字根据哈希函数放入哈希表的指定位置
      Hashfunc(str);
    }

    fclose(fp);
}

//将关键字根据哈希函数放入哈希表的指定位置 哈希表的插入操作
void MainWindow::Hashfunc(char str[]){

    int key, len;

    //数组长度赋给len
    len = strlen(str);

    //根据字符数组，获得该字符数组计算出关键码
    key = getkey(str, len);

    //以下循环是为了处理冲突，选用线性探测法
    while (hasht[key % 41].hash1 != NULL){

        key++;
    }

    //创建一个长度为len+1的一个数组
    hasht[key % 41].hash1 = (char*)malloc(sizeof(char) * (len + 1));

    //数组第一个元素的地址给了hasht[key%41].hash1指针
    strcpy(hasht[key % 41].hash1, str);

}

//将关键字根据哈希函数放入哈希表的指定位置 哈希表的插入操作
void MainWindow::Hashfunc1(char str[]){

    int key, len;

    //数组长度赋给len
    len = strlen(str);

    //根据字符数组，获得该字符数组计算出关键码
    key = getkey1(str, len);

    //以下循环是为了处理冲突，选用线性探测法
    while (hasht1[key % 81].hash1 != NULL){

        key++;
    }

    //创建一个长度为len+1的一个数组
    hasht1[key % 81].hash1 = (char*)malloc(sizeof(char) * (len + 1));
    hasht1[key % 81].count++;
    //数组第一个元素的地址给了hasht[key%41].hash1指针
    strcpy(hasht1[key % 81].hash1, str);

}

//在哈希表中找是否该words为关键字，并统计频度数
int MainWindow::Hashfind(char* words){

    int key, len, find;

    //得到存放一个单词的数组长度
    len = strlen(words);

    //得到该单词在哈希函数下的key
    key = getkey(words, len);

    //哈希表中key位置为空，说明words不是关键字，返回
    if(hasht[key].hash1 == NULL){

        return -1;
    }

    //判断读取的单词是否为关键词
    if (strcmp(hasht[key].hash1, words) == 0){

        //统计关键词的频度数
        hasht[key].count++;
        return 1;
    }

    /*
     * 如果不在key位置则向往后线性查找，
     * 然后再从头找（下一个for循环）
    */

    //线性探测法顺序查找哈希表中是否已存在关键字
    for (find = key + 1; find < HASHLEN; find++){

        /*先判断hasht[find].hash1是否空，
         * 再比较words与关键码是否相同
        */
        if (hasht[find].hash1 != NULL){

            if ( strcmp(hasht[find].hash1, words)== 0){

                //统计关键词的频度数
                hasht[find].count++;
                return 1;
            }
        }
    }

    /*如果key后面没找到，
     * 则从头找，原理和上个for循环一样
    */
    for (find = 0; find < key; find++){

        if (hasht[find].hash1 != NULL){

            if (strcmp(hasht[find].hash1, words) == 0){

                //统计关键词的频度数
                hasht[find].count++;
                return 1;
            }
        }
    }

    //Key前后都没找到，再返回0
    return -1;
}

//在哈希表中找是否该words为关键字，并统计频度数
int MainWindow::Hashfind1(char* words){

    int key, len, find;

    //得到存放一个单词的数组长度
    len = strlen(words);

    //得到该单词在哈希函数下的key
    key = getkey1(words, len);

    //哈希表中key位置为空，说明words不是关键字，返回
    if(hasht1[key].hash1 == NULL){

        return -1;
    }

    //判断读取的单词是否为关键词
    if (strcmp(hasht1[key].hash1, words) == 0){


        return key;
    }

    /*
     * 如果不在key位置则向往后线性查找，
     * 然后再从头找（下一个for循环）
    */

    //线性探测法顺序查找哈希表中是否已存在关键字
    for (find = key + 1; find < 81; find++){

        /*先判断hasht[find].hash1是否空，
         * 再比较words与关键码是否相同
        */
        if (hasht1[find].hash1 != NULL){

            if ( strcmp(hasht1[find].hash1, words)== 0){


                return find;
            }
        }
    }

    /*如果key后面没找到，
     * 则从头找，原理和上个for循环一样
    */
    for (find = 0; find < key; find++){

        if (hasht1[find].hash1 != NULL){

            if (strcmp(hasht1[find].hash1, words) == 0){


                return find;
            }
        }
    }

    //Key前后都没找到，再返回0
    return -1;
}

//判断是否ch为字母
int MainWindow::isletter(char ch){

    if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')){

        return 1;
    }
    else return 0;
}

/*
 * 读取源程序文件中的单词，
 * 参数I只能是0或1，表示读取那个文件
 * 0代表第一个文件
 * 1代表第二个文件
*/
int MainWindow::readc(int I){

    FILE* fp1 = NULL;
    char words[MAXLEN+1], ch;
    int i;

    /*
     * 如果I=0，读取第一个文件；
     *如果I=1，读取第二个文件
    */
    if (I == 0) {

        /*
         * 因为fopen（const char*）的参数是const char*类型，
         * 所以需要做如下两个转化
        */

        //将QString类型的字符串转成string类型
        string name1= file1.toStdString();

        //将string类型的字符串转成const char*;
        const char* NAM = name1.c_str();

        if ((fp1 = fopen(NAM, "r")) == NULL) {
            QString QQ = "失败1";
            ui->label_7->setText(QQ);
        }
    }
    else if (I == 1) {

        //和以上两个同理
        string name2= file2.toStdString();
        const char* NA = name2.c_str();

        if ((fp1 = fopen(NA, "r")) == NULL) {

            QString QQ = "失败1";
            ui->label_8->setText(QQ);
        }
    }

    //当文件读取完，循环结束
    while (!feof(fp1)){

        //每次读取都要让i=0,即从数组下标0开始存
        i = 0;

        //一个字符一个字符的读,fgetc返回读取的一个字节 int类型
        ch = fgetc(fp1);

        /*
         * isletter()不为字母时返回0
         * 如果读取的ch为字母，则退出循环
         * 如果读取的ch不是字母，
         * 就不断的从文件按顺序读取字符，
         * 直到找到字母或者到文件尾，就退出循环
        */
        while (isletter(ch) == 0 && feof(fp1) == 0){

            //fgetc()返回所读取的一个字节
            ch = fgetc(fp1);
        }

        /*对feof()来说,它的工作原理是，
         * 站在光标所在位置，向后看看还有没有字符。
         * 如果有，返回0；如果没有，返回非0。
         *它并不会读取相关信息，
         *只是查看光标后是否还有内容。
        */

        //读取字母且未完
        while (isletter(ch) && !feof(fp1)){

            /*
             * 规定关键字数组长度MAXLEN为20
             * 如果关键字数组长度等于20,
             * 就读取下一个单词
            */
            if (i == MAXLEN){

                //往下读取，找到字母，跳出循环
                while (isletter(ch) == 1 && feof(fp1) == 0){

                    ch = fgetc(fp1);
                }

                //读取下一个单词，所以字符数组从0开始
                i = 0;

                /*
                 * 因为要读取下一个单词，所以要跳出循环
                 * 得到的该单词调入Hashfind函数，
                 * 判断是否为关键字，并统计频度数
                 * 再继续读取下一个单词
                */
                break;
            }
            else{

                //将读取的字母存入数组
                words[i++] = ch;
                ch = fgetc(fp1);
            }
        }

        //将最后一个字符赋值'\0'表示字符结束
        words[i] = '\0';

        //将得到的该单词调入Hashfind函数，来判断是否为关键字，并统计频度数
        int DD = Hashfind(words);
        if(DD==-1){
            int Dd = Hashfind1(words);
            if(Dd==-1&&I==0){
                Hashfunc1(words);
            }
            else{
                if(I==0){
                    hasht1[Dd].count++;
                }
                else if(I==1){
                    hasht1[Dd].count1++;
                 }
                }
        }
    }

    //结束文件流的读取
    fclose(fp1);

    return 0;
}

//求欧式距离
int MainWindow::OM_D(int* x1, int* x2){

    int i = 0, sum = 0;

    /*
     * 根据这个公式计算：
     * d=sqrt( ∑(xi1-xi2)2 )
     * （两个向量的各分量平方和开根号）
    */
    for (i = 0; i < N; i++){

        sum += (x1[i] - x2[i])*(x1[i] - x2[i]);
    }

    return sqrt(sum);
}

//求街区距离
int MainWindow::STREET_D(int* x1, int* x2){

    int i = 0, sum = 0;

    /*
     * 根据这个公式计算：d = ∑|X1i-X2i|
     * (两个向量的各分量绝对值求和)
    */
    for (i = 0; i < N; i++){

        //用以下办法解决求两个数差的绝对值问题
        if(x1[i]>=x2[i]){

            sum += (x1[i] - x2[i]);
        }
        else{

            sum += (x2[i] - x1[i]);
        }

    }
    return sum;
}

//求欧式距离
int MainWindow::OM_D1(int* x3, int* x4){

    int i = 0, sum = 0;

    /*
     * 根据这个公式计算：
     * d=sqrt( ∑(xi1-xi2)2 )
     * （两个向量的各分量平方和开根号）
    */
    for (i = 0; i < 81; i++){

        sum += (x3[i] - x4[i])*(x3[i] - x4[i]);
    }

    return sqrt(sum);
}

//求街区距离
int MainWindow::STREET_D1(int* x3, int* x4){

    int i = 0, sum = 0;

    /*
     * 根据这个公式计算：d = ∑|X1i-X2i|
     * (两个向量的各分量绝对值求和)
    */
    for (i = 0; i < 81; i++){

        //用以下办法解决求两个数差的绝对值问题
        if(x3[i]>=x4[i]){

            sum += (x3[i] - x4[i]);
        }
        else{

            sum += (x4[i] - x3[i]);
        }

    }
    return sum;
}

//根据欧式距离计算相似度
double MainWindow::OM_similarity(int k){

    return exp(-k*OM_D(x1,x2));
}

//根据街区距离计算相似度
double MainWindow::STREET_similarity(int k){

    return exp(-k*STREET_D(x1,x2));
}

//选择第一个文件的按钮监听函数
void MainWindow::on_pushButton_clicked() {

    //获取第一个文件的地址
    file1 = QFileDialog::getOpenFileName(this,"文件一");

    //显示
    ui->label_4->setText(file1);
}

//选择第二个文件的按钮监听函数
void MainWindow::on_pushButton_2_clicked() {

    //获取第二个文件的地址
    file2 = QFileDialog::getOpenFileName(this,"文件二");

    //显示
    ui->label_5->setText(file2);
}

//检查的按钮监听函数
void MainWindow::on_pushButton_3_clicked(){

        //存储频度数的数组
        int x1[HASHLEN], x2[HASHLEN];

        //完全重置哈希表，即哈希表指针位置为NULL，频数为0
        resethash(0);

        //通过文件keyword.txt创建哈希表
        creathash();

        //读取第一个测试源程序文件，并且统计了该文件中关键词的频度数
        readc(0);

        //将统计好的频数复制给X数组
        copycount(x1, HASHLEN);

        //仅仅将频数count置为0
        resethash(1);

        //读取第二个测试源程序文件 ，并且统计了该文件中关键词的频度数
        readc(1);

        //将统计好的频数复制给x数组
        copycount(x2, HASHLEN);


        for(int i=0;i<81;i++){

            x3[i] = hasht1[i].count;
            x4[i] = hasht1[i].count1;
        }

        //计算欧拉距离并显示
        ui->label_3->setText(QString::number(OM_D(x1,x2)));
        ui->label_15->setText(QString::number(OM_D1(x3,x4)));

        //计算街区距离并显示
        ui->label_10->setText(QString::number(STREET_D(x1,x2)));
        ui->label_16->setText(QString::number(STREET_D1(x3,x4)));





       // 遍历
        for(double i=1;i>-20;i--){

            QString QS1 = QString::number(OM_similarity(i),'f',3);
            ui->textEdit->append(QS1);

            QString QS2 = QString::number(STREET_similarity(i),'f',3);
            ui->textEdit_2->append(QS2);
        }

}

//选择关键词文件的按钮监听函数
void MainWindow::on_pushButton_4_clicked(){

    //获取关键词文件的地址
    keyword_file = QFileDialog::getOpenFileName(this,"选择关键词文件");

    //显示
    ui->label_6->setText(keyword_file);
}
